﻿// <copyright file="ListNotifyCollectionChangedAdapter.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The ListNotifyCollectionChangedAdapter class
// </summary>


namespace SubhadraSolutions.Sharp.Utils.Collections.Generic
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;

    using SubhadraSolutions.Sharp.Utils.Threading;

    /// <summary>
    /// The ListNotifyCollectionChangedAdapter class
    /// </summary>
    /// <typeparam name="T">
    /// </typeparam>
    public class ListNotifyCollectionChangedAdapter<T> : AbstractListDecorator<T>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public virtual event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public virtual event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// The adapted list as atomic operation supported
        /// </summary>
        private readonly IAtomicOperationSupported adaptedListAsAtomicOperationSupported;

        /// <summary>
        /// Initializes a new instance of the <see cref="ListNotifyCollectionChangedAdapter{T}" /> class.
        /// </summary>
        /// <param name="adaptedList">The adapted list.</param>
        public ListNotifyCollectionChangedAdapter(IList<T> adaptedList)
            : base(adaptedList)
        {
            this.adaptedListAsAtomicOperationSupported = adaptedList as IAtomicOperationSupported;
        }

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        public override void Add(T item)
        {
            var action = new Action(delegate
                {
                    base.Add(item);
                    int index = base.Count - 1;

                    this.OnCountChanged();
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
                });
            this.PerformAction(action);
        }

        /// <summary>
        /// The clear.
        /// </summary>
        public override void Clear()
        {
            var action = new Action(delegate
                {
                    base.Clear();
                    this.OnCountChanged();
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                });
            this.PerformAction(action);
        }

        /// <summary>
        /// Removes the specified item.
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public override bool Remove(T item)
        {
            int index = base.IndexOf(item);
            if (index > -1)
            {
                var action = new Action(delegate
                    {
                        bool returnValue = base.Remove(item);
                        if (returnValue)
                        {
                            this.OnCountChanged();
                            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
                        }
                    });
                this.PerformAction(action);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Inserts the specified index.
        /// </summary>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        public override void Insert(int index, T item)
        {
            var action = new Action(delegate
                {
                    base.Insert(index, item);
                    this.OnCountChanged();
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
                });
            this.PerformAction(action);
        }

        /// <summary>
        /// Removes at.
        /// </summary>
        /// <param name="index">
        /// The index.
        /// </param>
        public override void RemoveAt(int index)
        {
            var action = new Action(delegate
                {
                    T item = base[index];
                    base.RemoveAt(index);
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
                    this.OnCountChanged();
                });
            this.PerformAction(action);
        }

        /// <summary>
        /// Gets or sets the <see cref="T"/> at the specified index.
        /// </summary>
        /// <value>
        /// The <see cref="T"/>.
        /// </value>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public override T this[int index]
        {
            set
            {
                var action = new Action(delegate
                    {
                        var oldValue = base[index];
                        base[index] = value;
                        this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldValue, value, index));
                    });
                this.PerformAction(action);
            }
        }

        #region Methods

        /// <summary>
        /// Raises the <see cref="E:CollectionChanged" /> event.
        /// </summary>
        /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var collectionChanged = this.CollectionChanged;
            if (collectionChanged != null)
            {
                collectionChanged(this, e);
            }
        }

        /// <summary>
        /// Called when [count changed].
        /// </summary>
        private void OnCountChanged()
        {
            var countChangedEventArgs = new PropertyChangedEventArgs("Count");
            var itemArrayChangedEventArgs = new PropertyChangedEventArgs("Item[]");
            this.OnPropertyChanged(countChangedEventArgs);
            this.OnPropertyChanged(itemArrayChangedEventArgs);
        }

        /// <summary>
        /// Raises the <see cref="E:PropertyChanged" /> event.
        /// </summary>
        /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var propertyChanged = this.PropertyChanged;
            if (propertyChanged != null)
            {
                propertyChanged(this, e);
            }
        }


        #endregion Methods

        /// <summary>
        /// Performs the action.
        /// </summary>
        /// <param name="action">The action.</param>
        private void PerformAction(Action action)
        {
            if (this.adaptedListAsAtomicOperationSupported == null)
            {
                action();
            }
            else
            {
                this.adaptedListAsAtomicOperationSupported.PerformAtomicOperation(action);
            }
        }
    }
}